#include "stdafx.h"
#include "stdlib/datetime.hpp"
#include "stdlib/bugcheck.hpp"
#include <algorithm>
#include <cmath>

namespace JHCPP
{
	namespace stdlib
	{	
		CDateTime::CDateTime()
		{
			CTimeStamp now;
			m_utcTime = now.utcTime();
			computeGregorian(julianDay());
			computeDaytime();
		}

		CDateTime::CDateTime(const CTimeStamp& timestamp) : m_utcTime(timestamp.utcTime())
		{
			computeGregorian(julianDay());
			computeDaytime();
		}

		CDateTime::CDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond)
		:m_year(year),
		m_month(month),
		m_day(day),
		m_hour(hour),
		m_minute(minute),
		m_second(second),
		m_millisecond(millisecond),
		m_microsecond(microsecond)
		{
			jh_assert (year >= 0 && year <= 9999);
			jh_assert (month >= 1 && month <= 12);
			jh_assert (day >= 1 && day <= daysOfMonth(year, month));
			jh_assert (hour >= 0 && hour <= 23);
			jh_assert (minute >= 0 && minute <= 59);
			jh_assert (second >= 0 && second <= 59);
			jh_assert (millisecond >= 0 && millisecond <= 999);
			jh_assert (microsecond >= 0 && microsecond <= 999);

			m_utcTime = toUtcTime(toJulianDay(year, month, day)) + 10*(hour*CTimeSpan::HOURS + minute*CTimeSpan::MINUTES + second*CTimeSpan::SECONDS + millisecond*CTimeSpan::MILLISECONDS + microsecond);
		}

		CDateTime::CDateTime(double julianDay) : m_utcTime(toUtcTime(julianDay))
		{
			computeGregorian(julianDay);
		}

		CDateTime::CDateTime(CTimeStamp::UtcTimeVal utcTime, CTimeStamp::TimeDiff diff) : m_utcTime(utcTime + diff*10)
		{
			computeGregorian(julianDay());
			computeDaytime();
		}

		CDateTime::CDateTime(const CDateTime& dateTime)
		:m_utcTime(dateTime.m_utcTime),
		m_year(dateTime.m_year),
		m_month(dateTime.m_month),
		m_day(dateTime.m_day),
		m_hour(dateTime.m_hour),
		m_minute(dateTime.m_minute),
		m_second(dateTime.m_second),
		m_millisecond(dateTime.m_millisecond),
		m_microsecond(dateTime.m_microsecond)
		{
		}

		CDateTime::~CDateTime()
		{
		}

		CDateTime& CDateTime::operator = (const CDateTime& dateTime)
		{
			if (&dateTime != this)
			{
				m_utcTime     = dateTime.m_utcTime;
				m_year        = dateTime.m_year;
				m_month       = dateTime.m_month;
				m_day         = dateTime.m_day;
				m_hour        = dateTime.m_hour;
				m_minute      = dateTime.m_minute;
				m_second      = dateTime.m_second;
				m_millisecond = dateTime.m_millisecond;
				m_microsecond = dateTime.m_microsecond;
			}
			return *this;
		}

		CDateTime& CDateTime::operator = (const CTimeStamp& timestamp)
		{
			m_utcTime = timestamp.utcTime();
			computeGregorian(julianDay());
			computeDaytime();
			return *this;
		}

		CDateTime& CDateTime::operator = (double julianDay)
		{
			m_utcTime = toUtcTime(julianDay);
			computeGregorian(julianDay);
			return *this;
		}

		CDateTime& CDateTime::assign(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond)
		{
			jh_assert (year >= 0 && year <= 9999);
			jh_assert (month >= 1 && month <= 12);
			jh_assert (day >= 1 && day <= daysOfMonth(year, month));
			jh_assert (hour >= 0 && hour <= 23);
			jh_assert (minute >= 0 && minute <= 59);
			jh_assert (second >= 0 && second <= 59);
			jh_assert (millisecond >= 0 && millisecond <= 999);
			jh_assert (microsecond >= 0 && microsecond <= 999);

			m_utcTime     = toUtcTime(toJulianDay(year, month, day)) + 10*(hour*CTimeSpan::HOURS + minute*CTimeSpan::MINUTES + second*CTimeSpan::SECONDS + millisecond*CTimeSpan::MILLISECONDS + microsecond);
			m_year        = year;
			m_month       = month;
			m_day         = day;
			m_hour        = hour;
			m_minute      = minute;
			m_second      = second;
			m_millisecond = millisecond;
			m_microsecond = microsecond;

			return *this;
		}

		void CDateTime::swap(CDateTime& dateTime)
		{
			std::swap(m_utcTime,	dateTime.m_utcTime);
			std::swap(m_year,		dateTime.m_year);
			std::swap(m_month,		dateTime.m_month);
			std::swap(m_day,		dateTime.m_day);
			std::swap(m_hour,		dateTime.m_hour);
			std::swap(m_minute,		dateTime.m_minute);
			std::swap(m_second,		dateTime.m_second);
			std::swap(m_millisecond,dateTime.m_millisecond);
			std::swap(m_microsecond,dateTime.m_microsecond);
		}

		int CDateTime::year() const
		{
			return m_year;
		}

		int CDateTime::month() const
		{
			return m_month;
		}

		int CDateTime::week(int firstDayOfWeek) const
		{
			jh_assert (firstDayOfWeek >= 0 && firstDayOfWeek <= 6);

			// find the first firstDayOfWeek.
			int baseDay = 1;
			while (CDateTime(m_year, 1, baseDay).dayOfWeek() != firstDayOfWeek) ++baseDay;

			int doy  = dayOfYear();
			int offs = baseDay <= 4 ? 0 : 1; 
			if (doy < baseDay)
				return offs;
			else
				return (doy - baseDay)/7 + 1 + offs;
		}

		int CDateTime::day() const
		{
			return m_day;
		}

		int CDateTime::dayOfWeek() const
		{
			return int((std::floor(julianDay() + 1.5))) % 7;
		}

		int CDateTime::dayOfYear() const
		{
			int doy = 0;
			for (int month = 1; month < m_month; ++month)
				doy += daysOfMonth(m_year, month);
			doy += m_day;
			return doy;
		}

		int CDateTime::hour() const
		{
			return m_hour;
		}

		int CDateTime::hourAMPM() const
		{
			if (m_hour < 1)
				return 12;
			else if (m_hour > 12)
				return m_hour - 12;
			else
				return m_hour;
		}

		bool CDateTime::isAM() const
		{
			return m_hour < 12;
		}

		bool CDateTime::isPM() const
		{
			return m_hour >= 12;
		}

		int CDateTime::minute() const
		{
			return m_minute;
		}

		int CDateTime::second() const
		{
			return m_second;
		}

		int CDateTime::millisecond() const
		{
			return m_millisecond;
		}

		int CDateTime::microsecond() const
		{
			return m_microsecond;
		}

		double CDateTime::julianDay() const
		{
			return toJulianDay(m_utcTime);
		}

		CTimeStamp CDateTime::timestamp() const
		{
			return CTimeStamp::fromUtcTime(m_utcTime);
		}

		CTimeStamp::UtcTimeVal CDateTime::utcTime() const
		{
			return m_utcTime;
		}

		bool CDateTime::operator == (const CDateTime& dateTime) const
		{
			return m_utcTime == dateTime.m_utcTime;
		}

		bool CDateTime::operator != (const CDateTime& dateTime) const	
		{
			return m_utcTime != dateTime.m_utcTime;
		}

		bool CDateTime::operator <  (const CDateTime& dateTime) const	
		{
			return m_utcTime < dateTime.m_utcTime;
		}

		bool CDateTime::operator <= (const CDateTime& dateTime) const
		{
			return m_utcTime <= dateTime.m_utcTime;
		}

		bool CDateTime::operator >  (const CDateTime& dateTime) const
		{
			return m_utcTime > dateTime.m_utcTime;
		}

		bool CDateTime::operator >= (const CDateTime& dateTime) const	
		{
			return m_utcTime >= dateTime.m_utcTime;
		}

		CDateTime CDateTime::operator + (const CTimeSpan& span) const
		{
			return CDateTime(m_utcTime, span.totalMicroseconds());
		}


		CDateTime CDateTime::operator - (const CTimeSpan& span) const
		{
			return CDateTime(m_utcTime, -span.totalMicroseconds());
		}


		CTimeSpan CDateTime::operator - (const CDateTime& dateTime) const
		{
			return CTimeSpan((m_utcTime - dateTime.m_utcTime)/10);
		}


		CDateTime& CDateTime::operator += (const CTimeSpan& span)
		{
			m_utcTime += span.totalMicroseconds()*10;
			computeGregorian(julianDay());
			computeDaytime();
			return *this;
		}


		CDateTime& CDateTime::operator -= (const CTimeSpan& span)
		{
			m_utcTime -= span.totalMicroseconds()*10;
			computeGregorian(julianDay());
			computeDaytime();
			return *this;
		}

		void CDateTime::makeUTC(int tzd)
		{
			operator -= (CTimeSpan(((CTimeStamp::TimeDiff) tzd)*CTimeSpan::SECONDS));
		}

		void CDateTime::makeLocal(int tzd)
		{
			operator += (CTimeSpan(((CTimeStamp::TimeDiff) tzd)*CTimeSpan::SECONDS));
		}

		bool CDateTime::isLeapYear(int year)
		{
			return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
		}

		int CDateTime::daysOfMonth(int year, int month)
		{
			jh_assert (month >= 1 && month <= 12);

			static int daysOfMonthTable[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

			if (month == 2 && isLeapYear(year))
				return 29;
			else
				return daysOfMonthTable[month];
		}

		bool CDateTime::isValid(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond)
		{
			return
				(year >= 0 && year <= 9999) &&
				(month >= 1 && month <= 12) &&
				(day >= 1 && day <= daysOfMonth(year, month)) &&
				(hour >= 0 && hour <= 23) &&
				(minute >= 0 && minute <= 59) &&
				(second >= 0 && second <= 59) &&
				(millisecond >= 0 && millisecond <= 999) &&
				(microsecond >= 0 && microsecond <= 999);
		}

		double CDateTime::toJulianDay(CTimeStamp::UtcTimeVal utcTime)
		{
			double utcDays = double(utcTime)/864000000000.0;
			return utcDays + 2299160.5; // first day of Gregorian reform (Oct 15 1582)
		}

		double CDateTime::toJulianDay(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond)
		{
			// lookup table for (153*month - 457)/5 - note that 3 <= month <= 14.
			static int lookup[] = {-91, -60, -30, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337};

			// day to double
			double dday = double(day) + ((double((hour*60 + minute)*60 + second)*1000 + millisecond)*1000 + microsecond)/86400000000.0;
			if (month < 3)
			{
				month += 12;
				--year;
			}
			double dyear = double(year);
			return dday + lookup[month] + 365*year + std::floor(dyear/4) - std::floor(dyear/100) + std::floor(dyear/400) + 1721118.5;
		}

		CTimeStamp::UtcTimeVal CDateTime::toUtcTime(double julianDay)
		{
			return CTimeStamp::UtcTimeVal((julianDay - 2299160.5)*864000000000.0);
		}

		void CDateTime::computeGregorian(double julianDay)
		{
			double z    = std::floor(julianDay - 1721118.5);
			double r    = julianDay - 1721118.5 - z;
			double g    = z - 0.25;
			double a    = std::floor(g / 36524.25);
			double b    = a - std::floor(a/4);
			m_year      = short(std::floor((b + g)/365.25));
			double c    = b + z - std::floor(365.25*m_year);
			m_month     = short(std::floor((5*c + 456)/153));
			double dday = c - std::floor((153.0*m_month - 457)/5) + r;
			m_day       = short(dday);
			if (m_month > 12)
			{
				++m_year;
				m_month -= 12;
			}
			r      *= 24;
			m_hour   = short(std::floor(r));
			r      -= std::floor(r);
			r      *= 60;
			m_minute = short(std::floor(r));
			r      -= std::floor(r);
			r      *= 60;
			m_second = short(std::floor(r));
			r      -= std::floor(r);
			r      *= 1000;
			m_millisecond = short(std::floor(r));
			r      -= std::floor(r);
			r      *= 1000;
			m_microsecond = short(r + 0.5);

			normalize();

			jh_assert_dbg (m_month >= 1 && m_month <= 12);
			jh_assert_dbg (m_day >= 1 && m_day <= daysOfMonth(m_year, m_month));
			jh_assert_dbg (m_hour >= 0 && m_hour <= 23);
			jh_assert_dbg (m_minute >= 0 && m_minute <= 59);
			jh_assert_dbg (m_second >= 0 && m_second <= 59);
			jh_assert_dbg (m_millisecond >= 0 && m_millisecond <= 999);
			jh_assert_dbg (m_microsecond >= 0 && m_microsecond <= 999);
		}

		void CDateTime::computeDaytime()
		{
			CTimeSpan span(m_utcTime/10);
			m_hour        = span.hours();
			m_minute      = span.minutes();
			m_second      = span.seconds();
			m_millisecond = span.milliseconds();
			m_microsecond = span.microseconds();
		}

		void CDateTime::checkLimit(short& lower, short& higher, short limit)
		{
			if (lower >= limit)
			{
				higher += short(lower / limit);
				lower   = short(lower % limit);
			}
		}

		void CDateTime::normalize()
		{
			checkLimit(m_microsecond, m_millisecond, 1000);
			checkLimit(m_millisecond, m_second, 1000);
			checkLimit(m_second, m_minute, 60);
			checkLimit(m_minute, m_hour, 60);
			checkLimit(m_hour, m_day, 24);

			if (m_day > daysOfMonth(m_year, m_month))
			{
				m_day -= daysOfMonth(m_year, m_month);
				if (++m_month > 12)
				{
					++m_year;
					m_month -= 12;
				}
			}
		}


	}//end of namespace stdlib
}//end of namespace JHCPP
